package checker

import (
	"kumachan/interpreter/lang/textual/ast"
	"kumachan/interpreter/lang/common/name"
	"kumachan/interpreter/lang/common/source"
	"kumachan/interpreter/compiler/checker/typsys"
	"kumachan/interpreter/compiler/checker/checked"
)


func checkBlank(B ast.TermBlank) ExprChecker {
	return makeExprChecker(B.Location, func(cc *checkContext) checkResult {
		return cc.error(E_BlankTerm {})
	})
}

func checkCast(arg_node ast.Expr, target ast.VariousType, loc source.Location) ExprChecker {
	return makeExprChecker(loc, func(cc *checkContext) checkResult {
		var type_ctx = cc.typeConsContext()
		var target_t, err1 = newType(target, type_ctx)
		if err1 != nil { return cc.propagate(err1) }
		var arg, err2 = cc.checkChildExpr(target_t, arg_node)
		if err2 != nil { return cc.propagate(err2) }
		return cc.assign(target_t, checked.Wrapper { Inner: arg })
	})
}

func checkInterior(arg *checked.Expr, mod_name ast.ModuleName, item_name ast.Identifier, loc source.Location) ExprChecker {
	return makeExprChecker(loc, func(cc *checkContext) checkResult {
		var mod_name_not_empty = mod_name.NotEmpty
		{ var i_def, i_args, op, base, is_interface = cc.unboxInteriorReferableInterfaceDef(arg)
		if !(is_interface) && mod_name_not_empty {
			return cc.propagate(source.MakeError(arg.Info.Location,
				E_DynamicCastFromNonInterface {
					Type: cc.describeType(arg.Type),
				}))
		}
		if is_interface {
			var c_name, err = NameFrom(
				mod_name, item_name, cc.exprContext.ModuleInfo,
			)
			if err != nil {
				return cc.propagate(err)
			}
			var ctx = cc.typeConsContext()
			var c_def_, c_def_desc, exists = ctx.ResolveTypeDefName(c_name)
			if !(exists) {
				return cc.propagate(source.MakeError(item_name.Location,
					E_TypeNotFound { Which: c_def_desc() }))
			}
			var c_def = c_def_.TypeDef
			var _, c_is_interface = c_def.Content.(*typsys.Interface)
			if c_is_interface {
				return cc.error(E_DynamicCastToInterface {})
			}
			var p = ImplPair {
				ConcreteType:  c_def,
				InterfaceType: i_def,
			}
			var table, ok = cc.exprContext.DispatchMapping[p]
			if !(ok) {
				var c_type_name = name.TypeName { Name: c_name }
				var i_type_name = i_def.Name
				return cc.error(
					E_NotImplementation {
						Type:      c_type_name.String(),
						Interface: i_type_name.String(),
					})
			}
			var c_type = &typsys.NestedType { Content: typsys.Ref {
				Def:  c_def,
				Args: i_args,
			} }
			var ref_type = cc.getType(coreCaseRef(
				base,
				c_type,
			))
			return cc.assign(
				ref_type,
				checked.InteriorRef {
					Base:    arg,
					Table:   table,
					Kind:    checked.RK_InterfaceCase,
					Operand: op,
				})
		} }
		{ var key = ast.Id2String(item_name)
		{ var record, op, base, is_record = cc.unboxInteriorReferableRecord(arg)
		if is_record {
			var index, exists = record.FieldIndexMap[key]
			if !(exists) { goto key_not_found }
			var field_t = record.Fields[index].Type
			var ref_t = cc.getType(coreProjRef(base, field_t))
			return cc.assign(
				ref_t,
				checked.InteriorRef {
					Base:    arg,
					Index:   index,
					Kind:    checked.RK_Proj,
					Operand: op,
				})
		} }
		{ var enum, ea, op, base, is_enum = cc.unboxInteriorReferableEnum(arg)
		if is_enum {
			var ci uint
			var cd *typsys.TypeDef
			var found = false
			for i, case_def := range enum.CaseTypes {
				if case_def.Name.ItemName == key {
					ci = uint(i)
					cd = case_def
					found = true
				}
			}
			if !(found) { goto key_not_found }
			var case_t = &typsys.NestedType { Content:
				typsys.Ref {
					Def:  cd,
					Args: ea,
				} }
			var ref_t = cc.getType(coreCaseRef(base, case_t))
			return cc.assign(
				ref_t,
				checked.InteriorRef {
					Base:    arg,
					Index:   ci,
					Kind:    checked.RK_Case,
					Operand: op,
				})
		} }
		key_not_found:
		return cc.propagate(source.MakeError(item_name.Location,
			E_NoSuchFieldOrCase {
				FieldName: key,
				TypeName:  cc.describeTypeOf(arg),
			})) }
	})
}

func checkList(L ast.Array) ExprChecker {
	return makeExprChecker(L.Location, func(cc *checkContext) checkResult {
		if len(L.Items) == 0 {
			return cc.assign(
				cc.getType(coreList(typsys.BottomType {})),
				checked.List {
					Items: [] *checked.Expr {},
					Info:  checked.ListInfo {},
				})
		}
		var item_t, ok = cc.getExpectedListItemType(cc.expected)
		if ok {
			var items = make([] *checked.Expr, len(L.Items))
			for i, item_node := range L.Items {
				var item, err = cc.checkChildExpr(item_t, item_node)
				if err != nil { return cc.throw(err) }
				items[i] = item
			}
			var item_t, err = cc.requireCertainOrInferredType(item_t)
			if err != nil { return cc.throw(err) }
			var list_t = cc.getType(coreList(item_t))
			return cc.assign(
				list_t,
				checked.List {
					Items: items,
					Info:  getListInfo(item_t),
				})
		} else {
			var item_t = typsys.Type(nil)
			var items = make([] *checked.Expr, len(L.Items))
			for i, item_node := range L.Items {
				var item, err = cc.checkChildExpr(item_t, item_node)
				if err != nil { return cc.propagate(err) }
				if item_t == nil {
					item_t = item.Type
				}
				items[i] = item
			}
			if item_t == nil { panic("something went wrong") }
			var list_t = cc.getType(coreList(item_t))
			return cc.assign(
				list_t,
				checked.List {
					Items: items,
					Info:  getListInfo(item_t),
				})
		}
	})
}

func checkPipelineLambda(P ast.PipelineLambda) ExprChecker {
	return checkQuickLambda(P.Node, quickPipelineLambda(P))
}
func quickPipelineLambda(P ast.PipelineLambda) quickLambda {
	return func(arg ast.Expr) ast.Expr {
		return ast.Expr {
			Node:     P.Node,
			Term:     ast.WrapExprIntoTerm(arg),
			Pipeline: P.Pipeline,
		}
	}
}

func checkConstructorLambda(C ast.ConstructorLambda) ExprChecker {
	return makeExprChecker(C.Location, func(cc *checkContext) checkResult {
		var type_ctx = cc.typeConsContext()
		var f, err = quickConstructorLambda(C, type_ctx)
		if err != nil { return cc.propagate(err) }
		return cc.forward(checkQuickLambda(C.Node, f))
	})
}
func quickConstructorLambda(C ast.ConstructorLambda, type_ctx TypeConsContext) (quickLambda, *source.Error) {
	var def, args, err = newRefNoCheckArgQuantity(C.Type, type_ctx)
	if err != nil { return nil, err }
	return func(arg ast.Expr) ast.Expr {
		return ast.Expr {
			Node: C.Node,
			Term: ast.VariousTerm {
				Node: C.Node,
				Term: ConstructorCallTerm {
					TypeDef:  def,
					TypeArgs: args,
					Argument: arg,
				},
			},
		}
	}, nil
}


